/**
 * 
 */
package org.wejde.muel;

import java.io.File;
import java.io.IOException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * @author webbpinner
 *
 */
public class muelConfigParser {

	public static final String DEFAULT_DELIMITER = ",";
	public static final String DEFAULT_TERMINATOR = "\n";
	public static final String DEFAULT_SEPERATOR = ":";
	
	private boolean validFlag;
	private String errorMsg;
	
	/**
	 * Global document object.
	 */
	private static Document doc;
	
	/**
	 * Number of tabs
	 */
	private int mainTabs;
	
	/**
	 * Title of Application
	 */
	private String appTitle;
	
	/**
	 * True if local logging is enabled.
	 */
	private boolean localLogging;
	
	/**
	 * True if udp logging is enabled.
	 */
	private boolean udpLogging;
	
	/**
	 * True if xmpp logging is enabled.
	 */
	private boolean xmppLogging;
	
	/**
	 * Delimiter to use when submitting events to the
	 * logging clients
	 */
	private String delimiter;
	
	/**
	 * Terminator to use when submitting events to the
	 * logging clients
	 */
	private String terminator;

	/**
	 * Filename base to use when logging locally
	 */
	private String localLogFilename;
	
	/**
	 * Username to prepend events submitted to the local
	 * logging and UDP clients
	 */
	private String username;
	
	/**
	 * Password to use when logging into the XMPP server.
	 * Not applicable with UDP or local logging.
	 */
	private String password;
	
	/**
	 * Server name to use when connecting to the UDP or
	 * XMPP servers.
	 */
	private String serverName;
	
	/**
	 * Network port to use when connecting to the UDP or
	 * XMPP servers.
	 */
	private int serverPort;
	
	/**
	 * Chatroom name to use with the XMPP server.  Not
	 * applicable with UDP or local logging clients.
	 */
	private String chatroomName;
	
	/**
	 * Array of fields added to the header pane.
	 */
	private fields[] headerFieldArray;
	
	/**
	 * Two-demensional array of fields used to populate all
	 * the tabbed panes.
	 */
	private fields[][] mainFieldArray;

	/**
	 * Array of strings used to name the tabs.
	 */
	private String[] tabNames;
	
	/**
	 * Constructor method. Parses the supplied an XML based on the supplied
	 * filename
	 * 
	 * @param xmlfile
	 *            filename of the XML-based configuration file.
	 */
	public muelConfigParser(String filename) {

		this.errorMsg = "";
		// DEBUG
		// System.err.println("Parsing config file: " + filename);

		// Parse the supplied XML file
		try {
			// Create a new XML document builder factory
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			
			DocumentBuilder builder = factory.newDocumentBuilder();

			// Check to see if the filename is actually a
			// URL (starts with "http://").
			if (filename.startsWith("http://")) {
				// URI uriname = new URI(fname);
				// inifile = new File(uriname);
				doc = builder.parse(filename);
			}
			// Otherwise, treat the configuration file as a
			// file on the local filesystem
			else {
				File xmlConfigFile = new File(filename);
				doc = builder.parse(xmlConfigFile);
			}
			this.validFlag = true;
		} catch (SAXException e) {
			//System.err.println(e.getMessage());
			//e.printStackTrace();
			this.errorMsg = "Please validate XML syntax.";
			this.validFlag = false;
		} catch (IOException e) {
			//System.err.println(e.getMessage());
			//e.printStackTrace();
			this.errorMsg = "Please verify file name and path.";
			this.validFlag = false;
		} catch (ParserConfigurationException e) {
			//System.err.println(e.getMessage());
			//e.printStackTrace();
			this.errorMsg = "Unknown parsing error.";
			this.validFlag = false;
		}

		if(this.validFlag) {
			// Parse out the Application title
			setAppName();
		
			// Parse out the logging client parameters
			setLogging();
		
			// Parse out the header fields
			setHeaderFields();
		
			// Parse out the number of tabs.
			setTabCount();
		
			// Parse out the tab names.
			setTabNames();
		
			// Parse out the fields used in all the tabs.
			setMainFields();
		
			// Set the delimiter to use when submitting events.
			setDelimiter();
		
			// Set the line terminator to use when submitting
			// events.
			setTerminator();
		}
	}

	/**
	 * Get the two-dimensional array of fields used to
	 * populate the tabbed panes
	 * 
	 * @return two-dimensional array of fields.
	 */
	public fields[][] getMainFields() {
		return this.mainFieldArray;
	}
	
	/**
	 * Get the names of the tab panes.
	 * 
	 * @return array of tab names.
	 */
	public String[] getTabNames() {
		return this.tabNames;
	}
	
	/**
	 * Get the fields for the header pane.
	 * 
	 * @return array of header fields
	 */
	public fields[] getHeaderFields() {
		return this.headerFieldArray;
	}

	/**
	 * Get the delimiter to use when submitting events
	 * 
	 * @return the delimiter
	 */
	public String getMuelDelim() {
		return this.delimiter;
	}

	/**
	 * Get the line terminator to use when submitting
	 * events.
	 * 
	 * @return the terminator
	 */
	public String getMuelTerm() {
		return this.terminator;
	}

	/**
	 * Get the basename of the local logfile.
	 * 
	 * @return the local logfile basename
	 */
	public String getLocalLogFilename() {
		return this.localLogFilename;
	}
	
	/**
	 * Is local logging enabled
	 * 
	 * @return true equals the local logging client is
	 * enabled.
	 */
	public boolean isLocalLogging() {
		return localLogging;
	}

	/**
	 * Is UDP logging enabled
	 * 
	 * @return true equals the UDP logging client is
	 * enabled.
	 */
	public boolean isUDPLogging() {
		return udpLogging;
	}

	/**
	 * Is XMPP logging enabled
	 * 
	 * @return true equals the XMPP logging client is
	 * enabled.
	 */
	public boolean isXMPPLogging() {
		return xmppLogging;
	}

	/**
	 * Is configuration file valid
	 * 
	 * @return true equals the configuration file is
	 * valid.
	 */
	public boolean isValid() {
		return validFlag;
	}
	/**
	 * Get the username used by the local, UDP and XMPP
	 * clients.
	 * 
	 * @return the username
	 */
	public String getUsername() {
		return username;
	}

	/**
	 * Get the password used by the XMPP client.
	 * 
	 * @return the password
	 */
	public String getPassword() {
		return this.password;
	}

	/**
	 * Get the server name used by the UDP and XMPP
	 * clients.
	 * 
	 * @return the server name
	 */
	public String getServerName() {
		return this.serverName;	
	}

	
	/**
	 * Get the server port used by the UDP and XMPP
	 * clients.
	 * 
	 * @return the server port
	 */
	public int getServerPort() {
		return serverPort;
	}

	/**
	 * Get the chatroom name used by the XMPP client.
	 * 
	 * @return the chatroom name
	 */
	public String getChatroomName() {
		return chatroomName;
	}

	/**
	 * Get the error message.
	 * 
	 * @return the error message
	 */
	public String getErrorMessage() {
		return errorMsg;
	}
	
	/**
	 * Set the username for the local, UDP and XMPP
	 * clients.
	 * 
	 * @param user the username
	 */
	public void setUsername(String user) {
		this.username = user;
	}
	
	
	/**
	 * Set the password for the XMPP clients.
	 * 
	 * @param password the password
	 */
	public void setPassword(String password) {
		this.password = password;
		
	}
	
	/**
	 * Get the Application title name to use.
	 * 
	 * @return the application title
	 */
	public String getAppName() {
		return this.appTitle;	
	}
	
	/**
	 * Set the number of tabbed panels
	 */
	private void setTabCount() {
		this.mainTabs = doc.getElementsByTagName("tabPane").getLength();
		// DEBUG
		//System.err.println("Number of Tabs: " + this.mainTabs);
	}
	
	/**
	 * Set the title of the application
	 */
	private void setAppName() {
		this.appTitle = doc.getElementsByTagName("general").item(0).getAttributes().getNamedItem("title").getNodeValue();
	}
	
	/**
	 * Set the delimiter
	 */
	private void setDelimiter() {
		Node nNode = doc.getElementsByTagName("general").item(0);
		if(nNode.getAttributes().getNamedItem("delim") != null) {
			this.delimiter = nNode.getAttributes().getNamedItem("delim").getNodeValue();
		} else {
			this.delimiter = muelConfigParser.DEFAULT_DELIMITER;
		}
	}

	/**
	 * Set the terminator
	 */
	private void setTerminator() {
		Node nNode = doc.getElementsByTagName("general").item(0);
		if(nNode.getAttributes().getNamedItem("term") != null) {
			this.terminator = nNode.getAttributes().getNamedItem("term").getNodeValue();
		} else {
			this.terminator = muelConfigParser.DEFAULT_TERMINATOR;
		}
	}

	
	/**
	 * Setup all the logging related information
	 */
	private void setLogging() {
		if(doc.getElementsByTagName("locallogging").getLength() > 0) {
			Node nNode = doc.getElementsByTagName("locallogging").item(0);
			if(nNode.getAttributes().getNamedItem("enable") != null) {
				if(nNode.getAttributes().getNamedItem("enable").getNodeValue().equals("true")) {
					this.localLogging = true;
				} else {
					this.localLogging = false;
				}
			} else {
				this.localLogging = true;
			}
			
			if(this.localLogging) {
				this.localLogFilename = nNode.getAttributes().getNamedItem("filename").getNodeValue();	
				if(nNode.getAttributes().getNamedItem("user") != null) {
					this.username = nNode.getAttributes().getNamedItem("user").getNodeValue();
				}
			}
		}
		if(doc.getElementsByTagName("xmpplogging").getLength() > 0) {
			Node nNode = doc.getElementsByTagName("xmpplogging").item(0);
			
			if(nNode.getAttributes().getNamedItem("enable") != null) {
				if(nNode.getAttributes().getNamedItem("enable").getNodeValue().equals("true")) {
					this.xmppLogging = true;
				} else {
					this.xmppLogging = false;
				}
			} else {
				this.xmppLogging = true;
			}

			if(this.xmppLogging) {
				this.serverName = nNode.getAttributes().getNamedItem("server").getNodeValue();	
				this.serverPort = Integer.parseInt(nNode.getAttributes().getNamedItem("port").getNodeValue());
				this.chatroomName = nNode.getAttributes().getNamedItem("room").getNodeValue();
				if(nNode.getAttributes().getNamedItem("user") != null) {
					this.username = nNode.getAttributes().getNamedItem("user").getNodeValue();
				}
				if(nNode.getAttributes().getNamedItem("pass") != null) {
					this.username = nNode.getAttributes().getNamedItem("pass").getNodeValue();
				}	
			}
		}
		if(doc.getElementsByTagName("udplogging").getLength() > 0) {
			Node nNode = doc.getElementsByTagName("udplogging").item(0);

			if(nNode.getAttributes().getNamedItem("enable") != null) {
				if(nNode.getAttributes().getNamedItem("enable").getNodeValue().equals("true")) {
					this.udpLogging = true;
				} else {
					this.udpLogging = false;
				}
			} else {
				this.udpLogging = true;
			}
			
			if(this.udpLogging) {
				this.serverName = nNode.getAttributes().getNamedItem("server").getNodeValue();	
				this.serverPort = Integer.parseInt(nNode.getAttributes().getNamedItem("port").getNodeValue());
				if(nNode.getAttributes().getNamedItem("user") != null) {
					this.username = nNode.getAttributes().getNamedItem("user").getNodeValue();
				}

			}
		}
	}
	
	/**
	 * Parse the configuration file for the fields to add
	 * to the header pane.
	 */
	private void setHeaderFields() {
		fields fieldArray[] = null;
		
		if(doc.getElementsByTagName("headerPane").getLength() > 0) {
			if(doc.getElementsByTagName("headerPane").getLength()==0) {
				this.headerFieldArray = null;
				return;
			}
			
			Node header = doc.getElementsByTagName("headerPane").item(0);
			NodeList fieldList = header.getChildNodes();
			fieldArray = new fields[getChildNodeCount(header, "field")];
			
			int i2 = 0;
			for (int i = 0; i < fieldList.getLength(); i++) {
				
				Node field = fieldList.item(i);
				if (field.getNodeType() == Node.ELEMENT_NODE && field.getNodeName().equals("field")) {
					fieldArray[i2] = new fields(i2);
					
					if(field.getAttributes().getNamedItem("type").getNodeValue().equals("checkbox")) {
						fieldArray[i2].setFieldType(fields.CHECKBOX);
					}
					else if(field.getAttributes().getNamedItem("type").getNodeValue().equals("textfield")) {
						fieldArray[i2].setFieldType(fields.TEXTFIELD);
					}
					else if(field.getAttributes().getNamedItem("type").getNodeValue().equals("combobox")) {
						fieldArray[i2].setFieldType(fields.COMBOBOX);
					}

					fieldArray[i2].setFieldLabel(field.getAttributes().getNamedItem("label").getNodeValue());
					fieldArray[i2].setFieldTooltip(field.getAttributes().getNamedItem("tooltip").getNodeValue());
					fieldArray[i2].setFieldClearOnSubmit(Boolean.parseBoolean(field.getAttributes().getNamedItem("clearonsubmit").getNodeValue()));
					fieldArray[i2].setFieldUseOnlyHidden(false);
					
					if(fieldArray[i2].getFieldType() == fields.CHECKBOX) {
						fieldArray[i2].setFieldUseOnlyHidden(true);
						String[] valueArray = {new String("")};
						fieldArray[i2].setFieldValue(valueArray);

						String[] hiddenValueArray = {new String("")};
						if(header.getAttributes().getNamedItem("hidden") != null) {
							hiddenValueArray[0] = header.getAttributes().getNamedItem("hidden").getNodeValue();
						}
						
						if(field.getAttributes().getNamedItem("hidden") != null) {
							if (!hiddenValueArray[0].equals("")) {
								hiddenValueArray[0] += muelConfigParser.DEFAULT_SEPERATOR; 
							}
							hiddenValueArray[0] = field.getAttributes().getNamedItem("hidden").getNodeValue();
						}
					}
					  
					else if(fieldArray[i2].getFieldType() == fields.TEXTFIELD) {
						String[] valueArray = {""};
						if (field.getAttributes().getNamedItem("value") != null) {
							valueArray[0] = field.getAttributes().getNamedItem("value").getNodeValue();
						}
						fieldArray[i2].setFieldValue(valueArray);
						  
						String[] hiddenValueArray = {""};
						if(header.getAttributes().getNamedItem("hidden") != null) {
							hiddenValueArray[0] = header.getAttributes().getNamedItem("hidden").getNodeValue();
						}
						
						if (field.getAttributes().getNamedItem("hidden") != null) {
							if (!hiddenValueArray[0].equals("")) {
								hiddenValueArray[0] += muelConfigParser.DEFAULT_SEPERATOR; 
							}
							hiddenValueArray[0] += field.getAttributes().getNamedItem("hidden").getNodeValue();
						}
						fieldArray[i2].setFieldHiddenValue(hiddenValueArray);
					}
					  
					else if(fieldArray[i2].getFieldType() == fields.COMBOBOX) {
						
						int childCount = getChildNodeCount(field, "value");
						
						NodeList valueList = field.getChildNodes();
						String valueArray[] = new String[childCount];
						String hiddenValueArray[] = new String[childCount];
						int k = 0;
						for (int j = 0; j < valueList.getLength(); j++) {
							
							Node value = valueList.item(j);
							if (value.getNodeType() == Node.ELEMENT_NODE && value.getNodeName().equals("value")) {
								// DEBUG
								//System.err.println("Value: " + value.getTextContent());
								valueArray[k] = value.getTextContent();
								
								if(header.getAttributes().getNamedItem("hidden") != null) {
									hiddenValueArray[k] = header.getAttributes().getNamedItem("hidden").getNodeValue();
								} else {
									hiddenValueArray[k] = "";
								}

								if(field.getAttributes().getNamedItem("hidden") != null) {
									if (!hiddenValueArray[k].equals("") && !hiddenValueArray[k].endsWith(muelConfigParser.DEFAULT_SEPERATOR)) {
										hiddenValueArray[k] += muelConfigParser.DEFAULT_SEPERATOR; 
									}
									hiddenValueArray[k] += field.getAttributes().getNamedItem("hidden").getNodeValue();
								}

								if(value.getAttributes().getNamedItem("hidden") != null) {
									hiddenValueArray[k] += value.getAttributes().getNamedItem("hidden").getNodeValue();
									fieldArray[i2].setFieldUseOnlyHidden(true);
								}
								k++;
							}
						}
						fieldArray[i2].setFieldValue(valueArray);
						fieldArray[i2].setFieldHiddenValue(hiddenValueArray);
					}
					i2++;
				}
			}
		}
		this.headerFieldArray = fieldArray;
	}
	
	/**
	 * Parse the configuration file for the fields to add
	 * to all of the tabbed panes.
	 */
	private void setMainFields() {
		int maxNumberOfFields = 0;
		for(int i = 0; i < this.mainTabs; i++) {
			if(doc.getElementsByTagName("tabPane").getLength() > 0) {
				Node tab = doc.getElementsByTagName("tabPane").item(i);
				if (getChildNodeCount(tab, "field") > maxNumberOfFields) {
					maxNumberOfFields = getChildNodeCount(tab, "field");
				}
			}
		}
		
		this.mainFieldArray = new fields[this.mainTabs][maxNumberOfFields];
		for(int i = 0; i < this.mainTabs; i++) {
			fields fieldArray[] = null;
			
			if(doc.getElementsByTagName("tabPane").getLength() > 0) {
				Node tab = doc.getElementsByTagName("tabPane").item(i);
				
				NodeList tabFields = tab.getChildNodes();
				
				int fieldCount = getChildNodeCount(tab, "field");
				fieldArray = new fields[fieldCount];
	
				int k = 0;
				for (int j = 0; j < tabFields.getLength(); j++) {
					
					Node field = tabFields.item(j);
					if (field.getNodeType() == Node.ELEMENT_NODE && field.getNodeName().equals("field")) {
						
						fieldArray[k] = new fields(i*maxNumberOfFields + k);
						
						if(field.getAttributes().getNamedItem("type").getNodeValue().equals("checkbox")) {
							fieldArray[k].setFieldType(fields.CHECKBOX);
						}
						else if(field.getAttributes().getNamedItem("type").getNodeValue().equals("textfield")) {
							fieldArray[k].setFieldType(fields.TEXTFIELD);
						}
						else if(field.getAttributes().getNamedItem("type").getNodeValue().equals("combobox")) {
							fieldArray[k].setFieldType(fields.COMBOBOX);
						}
						
						fieldArray[k].setFieldLabel(field.getAttributes().getNamedItem("label").getNodeValue());
						
						if(field.getAttributes().getNamedItem("tooltip") != null) {
							fieldArray[k].setFieldTooltip(field.getAttributes().getNamedItem("tooltip").getNodeValue());
						} else {
							fieldArray[k].setFieldTooltip("");
						}
						
						if(field.getAttributes().getNamedItem("clearonsubmit") != null) {
							fieldArray[k].setFieldClearOnSubmit(Boolean.parseBoolean(field.getAttributes().getNamedItem("clearonsubmit").getNodeValue()));
						} else {
							fieldArray[k].setFieldClearOnSubmit(true);
						}
						
						fieldArray[k].setFieldUseOnlyHidden(false);
						
						if(fieldArray[k].getFieldType() == fields.CHECKBOX) {
							fieldArray[k].setFieldUseOnlyHidden(true);
							String[] valueArray = {new String("")};
							fieldArray[k].setFieldValue(valueArray);
							
							String[] hiddenValueArray = {""};
							if(tab.getAttributes().getNamedItem("hidden") != null) {
								hiddenValueArray[0] = tab.getAttributes().getNamedItem("hidden").getNodeValue();
							}

							if(field.getAttributes().getNamedItem("hidden") != null) {
								if (!hiddenValueArray[0].equals("")) {
									hiddenValueArray[0] += muelConfigParser.DEFAULT_SEPERATOR; 
								}
								hiddenValueArray[0] += field.getAttributes().getNamedItem("hidden").getNodeValue();
								fieldArray[k].setFieldHiddenValue(hiddenValueArray);
							}
						}
						  
						else if(fieldArray[k].getFieldType() == fields.TEXTFIELD) {
							String[] valueArray = {""};
							if (field.getAttributes().getNamedItem("value") != null) {
								valueArray[0] = field.getAttributes().getNamedItem("value").getNodeValue();
							}
							fieldArray[k].setFieldValue(valueArray);
							  
							String[] hiddenValueArray = {""};
							if(tab.getAttributes().getNamedItem("hidden") != null) {
								hiddenValueArray[0] = tab.getAttributes().getNamedItem("hidden").getNodeValue();
							}
							
							if (field.getAttributes().getNamedItem("hidden") != null) {
								if (!hiddenValueArray[0].equals("")) {
									hiddenValueArray[0] += muelConfigParser.DEFAULT_SEPERATOR; 
								}
								hiddenValueArray[0] += field.getAttributes().getNamedItem("hidden").getNodeValue();
							}
							fieldArray[k].setFieldHiddenValue(hiddenValueArray);
						}
						  
						else if(fieldArray[k].getFieldType() == fields.COMBOBOX) {
							
							int childCount = getChildNodeCount(field, "value");
							
							NodeList valueList = field.getChildNodes();
							String valueArray[] = new String[childCount];
							String hiddenValueArray[] = new String[childCount];
							int k2 = 0;
							for (int j2 = 0; j2 < valueList.getLength(); j2++) {
								
								Node value = valueList.item(j2);
								if (value.getNodeType() == Node.ELEMENT_NODE && value.getNodeName().equals("value")) {
									// DEBUG
									//System.err.println("Value: " + value.getTextContent());
									valueArray[k2] = value.getTextContent();

									if(tab.getAttributes().getNamedItem("hidden") != null) {
										hiddenValueArray[k2] = tab.getAttributes().getNamedItem("hidden").getNodeValue();
									} else {
										hiddenValueArray[k2] = "";
									}
									
									if(field.getAttributes().getNamedItem("hidden") != null) {
										if (!hiddenValueArray[k2].equals("") && !hiddenValueArray[k2].endsWith(muelConfigParser.DEFAULT_SEPERATOR)) {
											hiddenValueArray[k2] += muelConfigParser.DEFAULT_SEPERATOR; 
										}
										hiddenValueArray[k2] += field.getAttributes().getNamedItem("hidden").getNodeValue();
									}

									if(value.getAttributes().getNamedItem("hidden") != null) {
										if (!hiddenValueArray[k2].equals("") && !hiddenValueArray[k2].endsWith(muelConfigParser.DEFAULT_SEPERATOR)) {
											hiddenValueArray[k2] += muelConfigParser.DEFAULT_SEPERATOR; 
										}
										hiddenValueArray[k2] += value.getAttributes().getNamedItem("hidden").getNodeValue();
										fieldArray[k].setFieldUseOnlyHidden(true);
									}
									k2++;
								}
							}
							fieldArray[k].setFieldValue(valueArray);
							fieldArray[k].setFieldHiddenValue(hiddenValueArray);
						}	
						k++;
					}
				}
			}
			this.mainFieldArray[i] = fieldArray;
		}
	}
	
	/**
	 * Parse the configuration file for the names to the
	 * tabbed panes
	 */
	private void setTabNames() {
		if(doc.getElementsByTagName("tabPane").getLength() > 0) {
			NodeList tabList = doc.getElementsByTagName("tabPane");
			this.tabNames = new String[tabList.getLength()];
			for(int i = 0; i < tabList.getLength(); i++) {
				this.tabNames[i] = tabList.item(i).getAttributes().getNamedItem("label").getNodeValue();
			}
		}
	}
	
	/**
	 * Parse the configuration file for the number of child
	 * nodes with the specified name
	 * 
	 * @param node the node to search for matching child
	 * nodes.
	 * @param name the name of the child node to count.
	 */
	private int getChildNodeCount(Node node, String name) {
		int N = node.getChildNodes().getLength();

		int Nelement = 0;
		for (int n = 0; n < N; n++) {
			Node child = node.getChildNodes().item(n);
			if (child.getNodeType() == Node.ELEMENT_NODE
					& child.getNodeName() == name) {
				Nelement++;
			}
		}
		return Nelement;
	}
}